﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HtmlAgilityPack;
using System.IO;
using System.Data;

namespace DataExtractionLanguage
{
    /// <summary>
    /// Represent the source paths of the internet pages, or html files on the hard disk.
    /// </summary>
    public class SourceList
    {
        /// <summary>
        /// Gets or sets the paths of the internet pages, or html files.
        /// </summary>
        public List<string> Paths { get; set; }

        /// <summary>
        /// Gets or sets the alias of all the paths which exists in the Paths property.
        /// </summary>
        public string Alias { get; set; }

        /// <summary>Columns which related to this SourceList, with its result.</summary>
        internal Dictionary<Column, HtmlNodeCollection> ColumnsResult { get; set; }

        /// <summary>Uses to get Html representation from a url or file path.</summary>
        private static HtmlWeb htmlWeb = new HtmlWeb();

        /// <summary>Cashe of an HtmlDocument representation for each path.</summary>
        private Dictionary<string, HtmlDocument> documents = new Dictionary<string, HtmlDocument>();

        public SourceList()
        {
            Paths = new List<string>();
        }

        /// <summary>
        /// Adding the path which is might be url or file path.
        /// </summary>
        /// <param name="path">URL or file path to add to the Paths List.</param>
        public bool AddPath(string path)
        {
            string dirPath = string.Empty;
            string extension = string.Empty;

            if (path.Contains("*."))
            {
                dirPath = path.Substring(0, path.IndexOf('*'));
                extension = path.Substring(path.IndexOf('*'));

                DirectoryInfo dirInfo = new DirectoryInfo(dirPath);

                if (!dirInfo.Exists)
                    return false;

                foreach (FileInfo fileInfo in dirInfo.GetFiles(extension, SearchOption.AllDirectories))
                    Paths.Add(fileInfo.FullName);
            }
            else if (path.StartsWith("http"))
                Paths.Add(path);
            else
                if (File.Exists(path))
                    Paths.Add(path);
                else
                    return false;

            return true;
        }

        private bool CheckCasheAndLoadPage(string path)
        {
            HtmlDocument htmlDoc = null;
            try
            {
                if (!documents.TryGetValue(path, out htmlDoc))
                {
                    htmlDoc = htmlWeb.Load(path);
                    documents.Add(path, htmlDoc);
                }
                return true;
            }
            catch
            {
                return false;
            }
        }

        public void InitHtmlDocument() // TODO: I have to return the boolean result of the CheckCasheAndLoadPage
        {
            foreach (string pathItem in Paths)
                CheckCasheAndLoadPage(pathItem);
        }

        internal DataTable Result { get; set; }
        internal int ResultRowPosition { get; set; }

        /// <summary>
        /// Gets the row count according to each column selection result count.
        /// This value is calaculated up to max selection result count.
        /// </summary>
        internal int RowCount { get; private set; }

        public void CreateResultTable()
        {
            Result = new DataTable(Alias);

            foreach (var item in ColumnsResult)
                Result.Columns.Add(item.Key.Alias, typeof(string));
        }

        /// <summary>
        /// Extracts data from the html document.
        /// </summary>
        /// <remarks>Affect RowCount property and give it's value.</remarks>
        public void ExtractData()
        {
            HtmlNodeCollection nodes = null;

            foreach (KeyValuePair<Column, HtmlNodeCollection> item in ColumnsResult)
            {
                nodes = SelectNodes(item.Key.ColumnXPath);

                foreach (HtmlNode node in nodes)
                    item.Value.Add(node);

                if (nodes.Count > RowCount)
                    RowCount = nodes.Count;
            }
        }

        /// <summary>
        /// Select the xpath result on each path document.
        /// </summary>
        /// <param name="xpath">XPath to bo selected.</param>
        /// <returns>HtmlNodes which are came from each document.</returns>
        public HtmlNodeCollection SelectNodes(string xpath)
        {
            HtmlNodeCollection nodeCollection = new HtmlNodeCollection(null);
            HtmlNodeCollection fileResult = null;

            foreach (var docEntry in documents)
            {
                fileResult = docEntry.Value.DocumentNode.SelectNodes(xpath);
                if (fileResult != null)
                    foreach (HtmlNode node in fileResult)
                        nodeCollection.Add(node);
            }

            return nodeCollection;
        }


        public override string ToString()
        {
            return Alias;
        }

        /// <summary>
        /// Build the columnResult dictionary which is going to contains selection result of each column.
        /// </summary>
        /// <param name="actualColumns"></param>
        internal void BuildColumnResult(List<Column> actualColumns)
        {
            ColumnsResult = new Dictionary<Column, HtmlNodeCollection>();

            foreach (Column col in actualColumns)
                if (col.SourceAlias == Alias)
                    ColumnsResult.Add(col.Clone(), new HtmlNodeCollection(null));
        }

        internal Column GetColumnByAlias(string columnAlias)
        {
            Column col = ColumnsResult.Keys.Where(x => x.Alias.ToLower() == columnAlias.ToLower()).FirstOrDefault();

            if (col == null)
                throw new Exception("there is not column with name {0} in {1} SourceList.");

            return col;
        }
    }
}
